using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using SystemFunctionLib.Apis;

namespace SystemFunctionLib.SystemEnvironment
{
    /// <summary>
    /// Registers, listens, and unregisters system hot keys.
    /// </summary>
    public class SystemHotKeyListener : IDisposable
    {
        /// <summary>
        /// Occurs when the registered system hot key was pressed.
        /// </summary>
        public event SystemHotKeyPressedEventHandler SystemHotKeyPressed;

        /// <summary>
        /// Occurs when the registered system hot key was double-pressed.
        /// </summary>
        public event SystemHotKeyPressedEventHandler SystemHotKeyDoublePressed;
        
        /// <summary>
        /// Initializes a new instance of the <see cref="SystemHotKeyListener"/>.
        /// </summary>
        public SystemHotKeyListener()
        {
            _listeningForm = new Form();
            _listeningForm.Visible = false;

            _messageFilter = new SystemHotKeyMessageFilter(this);
            Application.AddMessageFilter(_messageFilter);
        }

        private Form _listeningForm = null;

        internal Dictionary<int, HotKey> _hotKeys = new Dictionary<int, HotKey>();
        public ICollection<HotKey> HotKeys
        {
            get { return _hotKeys.Values as ICollection<HotKey>; }
        }

        private SystemHotKeyMessageFilter _messageFilter = null;

        private int nextId = 100;
        
        /// <summary>
        /// Registers a new system hot key.
        /// </summary>
        /// <param name="hotKey">The <see cref="HotKey"/> that need to register.</param>
        /// <exception cref="InvalidOperationException">
        /// The same hot key or name already exists.
        /// </exception>
        public void Register(HotKey hotKey)
        {
            // Check for duplicate hot key name.
            foreach (HotKey existingHotKey in _hotKeys.Values)
            {
                if (existingHotKey.Name == hotKey.Name)
                {
                    throw new InvalidOperationException("The hot key with name of '" + hotKey.Name + "' has been already registered.");
                }

                if (existingHotKey.Modifiers == hotKey.Modifiers &&
                    existingHotKey.Key == hotKey.Key)
                {
                    throw new InvalidOperationException("The same hot key has been already registered.");
                }
            }

            // Register the hot key.
            hotKey.Id = nextId;
            _hotKeys.Add(nextId, hotKey);
            WinApis.RegisterHotKey(_listeningForm.Handle, nextId, (uint)(hotKey.Modifiers), hotKey.Key);

            // Increase the internal hot key id.
            nextId++;
        }

        /// <summary>
        /// Unregisters the specific hot key.
        /// </summary>
        /// <param name="hotKey">The <see cref="HotKey"/> that needs to be unregistered.</param>
        /// <returns>A <see cref="bool"/> value that indicates whether the operation is successed.</returns>
        public bool Unregister(HotKey hotKey)
        {
            if (_hotKeys.ContainsKey(hotKey.Id))
            {
                WinApis.UnregisterHotKey(_listeningForm.Handle, hotKey.Id);
                _hotKeys.Remove(hotKey.Id);
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Unregisters all system hot keys registered by this program.
        /// </summary>
        public void UnregisterAll()
        {
            int[] idList = new int[_hotKeys.Count];
            _hotKeys.Keys.CopyTo(idList, 0);

            for (int i = 0; i < idList.Length; i++)
            {
                int id = idList[i];

                WinApis.UnregisterHotKey(_listeningForm.Handle, id);
                _hotKeys.Remove(id);
            }
        }

        protected HotKey _lastPressedHotKey = null;
        protected DateTime _lastPressedTime = DateTime.MinValue;

        internal void InvokeSystemHotKeyPressed(HotKey hotKey)
        {
            SystemHotKeyPressedEventArgs e = new SystemHotKeyPressedEventArgs(hotKey);

            if (_lastPressedHotKey == hotKey)
            {
                TimeSpan diff = DateTime.Now.Subtract(_lastPressedTime);
                if (diff.TotalMilliseconds <= SystemInformation.DoubleClickTime)
                {
                    // Double click
                    _lastPressedTime = DateTime.MinValue;

                    OnSystemHotKeyDoublePressed(e);
                    return;
                }
            }

            // Single click
            _lastPressedHotKey = hotKey;
            _lastPressedTime = DateTime.Now;

            OnSystemHotKeyPressed(e);
        }

        protected virtual void OnSystemHotKeyPressed(SystemHotKeyPressedEventArgs e)
        {
            if (SystemHotKeyPressed != null)
            {
                SystemHotKeyPressedEventHandler invoker = SystemHotKeyPressed;
                invoker(this, e);
            }
        }

        protected virtual void OnSystemHotKeyDoublePressed(SystemHotKeyPressedEventArgs e)
        {
            if (SystemHotKeyDoublePressed != null)
            {
                SystemHotKeyPressedEventHandler invoker = SystemHotKeyDoublePressed;
                invoker(this, e);
            }
        }

        #region IDisposable Members

        /// <summary>
        /// Releases all the resources used by the <see cref="SystemHotKeyListener"/>.
        /// </summary>
        public void Dispose()
        {
            UnregisterAll(); 
            _listeningForm.Dispose();
        }

        #endregion
    }
}
